Contents
  1. 1. 一.什么是Java对象序列化
  2. 2. 二.Java序列化的作用
  3. 3. 三.实现java对象的序列化和反序列化。
    1. 3.1. 注意:
    2. 3.2. Serializable的作用
    3. 3.3. 补充:
  4. 4. 日后更新:

参考好文:
http://www.cnblogs.com/xiohao/p/4234184.html
http://www.blogjava.net/jiangshachina/archive/2012/02/13/369898.html

《Thinking in java》

一.什么是Java对象序列化

  Java平台允许我们在内存中创建可复用的Java对象,但一般情况下,只有当JVM处于运行时,这些对象才可能存在,即,这些对象的生命周期不会比JVM的生命周期更长。但在现实应用中,就可能要求在JVM停止运行之后能够保存(持久化)指定的对象,并在将来重新读取被保存的对象。Java对象序列化就能够帮助我们实现该功能。
  使用Java对象序列化,在保存对象时,会把其状态保存为一组字节,在未来,再将这些字节组装成对象。必须注意地是,对象序列化保存的是对象的”状态”,即它的成员变量。由此可知,对象序列化不会关注类中的静态变量。
  除了在持久化对象时会用到对象序列化之外,当使用RMI(远程方法调用),或在网络中传递对象时,都会用到对象序列化。Java序列化API为处理对象序列化提供了一个标准机制,该API简单易用,在本文的后续章节中将会陆续讲到。

二.Java序列化的作用

  有的时候我们想要把一个Java对象变成字节流的形式传出去,有的时候我们想要从一个字节流中恢复一个Java对象。例如,有的时候我们想要把一个Java对象写入到硬盘或者传输到网路上面的其它计算机,这时我们就需要自己去通过java把相应的对象写成转换成字节流。对于这种通用的操作,我们为什么不使用统一的格式呢?没错,这里就出现了java的序列化的概念。
  在Java的OutputStream类下面的子类ObjectOutputStream类就有对应的WriteObject(Object object) 其中要求对应的object实现了java的序列化的接口。
  为了更好的理解java序列化的应用,我举两个自己在开发项目中遇到的例子:
1)在使用tomcat开发JavaEE相关项目的时候,我们关闭tomcat后,相应的session中的对象就存储在了硬盘上,如果我们想要在tomcat重启的
时候能够从tomcat上面读取对应session中的内容,那么保存在session中的内容就必须实现相关的序列化操作。
2)如果我们使用的java对象要在分布式中使用或者在rmi远程调用的网络中使用的话,那么相关的对象必须实现java序列化接口。

三.实现java对象的序列化和反序列化。

Java对象的序列化有两种方式。

  • a.是相应的对象实现了序列化接口Serializable,这个使用的比较多,对于序列化接口Serializable接口它的主要作用就是标识这个对象时可序列化的,

注意:

  • java中的序列化时transient变量(这个关键字的作用就是告知JAVA我不可以被序列化)和静态变量不会被序列化。

  • 静态变量不属于对象,属于类。不能被序列化。还有瞬态的变量也不能被序列化 。

  • 序列化保存的时对象的状态,静态变量属于类的状态,所以序列化并不保存静态变量。

  • 具体是什么个样子,后面会解释的

  • 每个枚举类型都会默认继承类java.lang.Enum,而该类实现了Serializable接口,所以枚举类型对象都是默认可以被序列化的。

Serializable的作用

  为什么一个类实现了Serializable接口,它就可以被序列化呢?在上节的示例中,使用ObjectOutputStream来持久化对象,在该类中有如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
1.	private void writeObject0(Object obj, boolean unshared) throws IOException {  
2. ...
3. if (obj instanceof String) {
4. writeString((String) obj, unshared);
5. } else if (cl.isArray()) {
6. writeArray(obj, desc, unshared);
7. } else if (obj instanceof Enum) {
8. writeEnum((Enum) obj, desc, unshared);
9. } else if (obj instanceof Serializable) {
10. writeOrdinaryObject(obj, desc, unshared);
11. } else {
12. if (extendedDebugInfo) {
13. throw new NotSerializableException(cl.getName() + "\n"
14. + debugInfoStack.toString());
15. } else {
16. throw new NotSerializableException(cl.getName());
17. }
18. }
19. ...
20. }

  从上述代码可知,如果被写对象的类型是String,或数组,或Enum,或Serializable,那么就可以对该对象进行序列化,否则将抛出NotSerializableException。补充:基本类型的包装类的对象,所有容器类的对象甚至Class对象都可以被序列化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package com.shop.domain;

public class Article implements java.io.Serializable {
private static final long serialVersionUID = 1L;//这个当做版本号,尽量显示声明且为private static final long型
private Integer id;
private String title; //文章标题
private String content; // 文章内容
private String faceIcon;//表情图标
private Date postTime; //文章发表的时间
private String ipAddr; //用户的ip

private User author; //回复的用户

public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getFaceIcon() {
return faceIcon;
}
public void setFaceIcon(String faceIcon) {
this.faceIcon = faceIcon;
}
public Date getPostTime() {
return postTime;
}
public void setPostTime(Date postTime) {
this.postTime = postTime;
}
public User getAuthor() {
return author;
}
public void setAuthor(User author) {
this.author = author;
}
public String getIpAddr() {
return ipAddr;
}
public void setIpAddr(String ipAddr) {
this.ipAddr = ipAddr;
}
}
  • b.实现序列化的第二种方式为实现接口Externalizable,该接口是继承于Serializable接口的

  • 并增加了两个方法:

    1
    2
    void writeExternal(ObjectOutput out) throws IOException;
    void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;

  首先,我们在序列化对象的时候,由于这个类实现了Externalizable 接口,在writeExternal()方法里定义了哪些属性可以序列化,哪些不可以序列化,所以,对象在经过这里就把规定能被序列化的序列化保存文件,不能序列化的不处理,然后在反序列的时候自动调用readExternal()方法,根据序列顺序挨个读取进行反序列,并自动封装成对象返回,然后在测试类接收,就完成了反序列。
所以说Exterinable的是Serializable的一个扩展。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
package yu;

import java.io.Externalizable;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.util.Scanner;

/*
* 测试实体类
*/

class Person implements Externalizable{

private static final long serialVersionUID = 123L;

String userName;
String password;
String age;

public Person(){
System.out.println(“无参构造器”);
}

public Person(String userName, String password, String age){
this.userName = userName;
this.password = password;
this.age = age;
System.out.println(“有参构造器”);
}

public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}

/**
* 序列化操作的扩展类
*/

@Override
public void writeExternal(ObjectOutput out) throws IOException {
// TODO Auto-generated method stub
//不泄露年龄
out.writeObject(userName);
out.writeObject(password);
}

/**
* 序列化操作的扩展类
*/

@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
// TODO Auto-generated method stub
//注意这里的接受顺序是有限制的哦,否则的话会出错的
// 例如上面先write的是A对象的话,那么下面先接受的也一定是A对象...
userName = (String)in.readObject();
password = (String)in.readObject();

}

@Override
public String toString(){
//注意这里的年龄是不会被序列化的,所以在反序列化的时候是读取不到数据的
return "用户名:" + userName + " 密码:" + password + " 年龄:" + age;
}
}

//序列化和反序列化的相关操作类
class Operate{

public Operate(){

}

//序列化方法
public void serializable(Person person) throws FileNotFoundException, IOException{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("k:/rot/ceshi.txt"));
oos.writeObject(person);
oos.close();
}
//反序列化方法
public Person deSerializable() throws FileNotFoundException, IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("k:/rot/ceshi.txt"));
Person temp = (Person)ois.readObject();
ois.close();
return temp;

}
}

//测试实体主类
public class lian {

public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException{

Operate operate = new Operate();
Person person = new Person("dabai", "123456", "18");
System.out.println("为序列化之前的相关数据如下:\n" + person.toString());
operate.serializable(person);
Person newPerson = operate.deSerializable();
System.out.println("-------------------------------------------------------");
System.out.println("序列化之后的相关数据如下:\n" + newPerson.toString());
}
}


  首先,我们在序列化UserInfo对象的时候,由于这个类实现了Externalizable 接口,序列化的细节需要由程序员去完成。在writeExternal()方法里定义了哪些属性可以序列化,哪些不可以序列化,所以,对象在经过这里就把规定能被序列化的序列化保存文件,不能序列化的不处理,然后在反序列的时候自动调用readExternal()方法,根据序列顺序挨个读取进行反序列,并自动封装成对象返回,然后在测试类接收,就完成了反序列。

  使用Externalizable进行序列化时,当读取对象时,会调用被序列化类的无参构造器去创建一个新的对象,然后再将被保存对象的字段的值分别填充到新对象中。这就是为什么在此次序列化过程中Person类的无参构造器会被调用。由于这个原因,实现Externalizable接口的类必须要提供一个无参的构造器,且它的访问权限为public。

  • java中的序列化时transient变量(这个关键字的作用就是告知JAVA我不可以被序列化)和静态变量不会被序列
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package yu;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

class Student1 implements Serializable{

private static final long serialVersionUID = 456123L;
private String name;
private transient String password;
private static int count = 0;

public Student1(){

}

public Student1(String name, String password){
System.out.println("调用Student的带参的构造方法");
this.name = name;
this.password = password;
count++;
}

public String toString(){
return "人数:" + count + " 姓名:" + name + " 密码:" + password;
}
}

public class Lian2 {

public static void main(String[] args){
try{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("k:/rot/ceshi.txt"));
Student1 s1 = new Student1("张三", "12345");
Student1 s2 = new Student1("王五", "54321");
oos.writeObject(s1);
oos.writeObject(s2);
oos.close();

ObjectInputStream ois = new ObjectInputStream(new FileInputStream("k:/rot/ceshi.txt"));
Student1 s3 = (Student1) ois.readObject();
Student1 s4 = (Student1) ois.readObject();
System.out.println(s3);
System.out.println(s4);
ois.close();
}catch(IOException e){
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}


  注意这里 count是类的,访问的时候是通过类来访问的没有参与对象的序列化

  静态成员属于类级别的,所以不能序列化这里的不能序列化的意思,是序列化信息中不包含这个静态成员域这个测试成功,是因为都在同一个机器(而且是同一个进程),因为我这里的jvm已经把count加载进来了,所以获取的是加载好的count,如果是传到另一台机器或者关掉程序重写写个程序读入test.obj,此时因为别的机器或新的进程是重新加载count的,所以count信息就是初始时的信息。

所以再写一个类来测试,就知道答案了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package yu;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class Lian3 {

public static void main(String args[]){

try {

FileInputStream fis = new FileInputStream("k:/rot/ceshi.txt");
ObjectInputStream ois = new ObjectInputStream(fis);

Student1 s3 = (Student1) ois.readObject();
Student1 s4 = (Student1) ois.readObject();

System.out.println(s3);
System.out.println(s4);

ois.close();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e1) {
e1.printStackTrace();
}
}
}

证明了,静态成员不会被序列化

补充:

  • 如果你先序列化对象A后序列化B,那么在反序列化的时候一定记着JAVA规定先读到的对象是先被序列化的对象,

  • 最好显示声明一个serialVersionUID,如果源代码中稍微改了一点点小地方,如果没有显示声明他,默认用算法算出的将是两个完全不同的数,反序列化的时候就会失败。

  • 是否声明serialVersionUID对于对象序列化的向上向下的兼容性有很大的影响。

日后更新:

三.实现序列化的其它方式
1)是把对象包装成JSON字符串传输。
2)采用谷歌的ProtoBuf
参考: http://www.cnblogs.com/xiohao/p/4234184.html

四、单例模式下的序列化
参考:http://developer.51cto.com/art/201202/317181.htm

Contents
  1. 1. 一.什么是Java对象序列化
  2. 2. 二.Java序列化的作用
  3. 3. 三.实现java对象的序列化和反序列化。
    1. 3.1. 注意:
    2. 3.2. Serializable的作用
    3. 3.3. 补充:
  4. 4. 日后更新: